/*
 ------------------------------------------------------------------------------
	sdlmedia - a test program for a media search library, including test UI.

	Copyright (C) 2011 Pierre Veber
	
	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 3 of the License, or
	(at your option) any later version.
	
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software Foundation,
	Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
------------------------------------------------------------------------------
*/

#include "common.h"
#include "thread.h"

#define THIS_FILTER	TRACE_THREAD
#define MESSAGE_QUEUE_SIZE		100

struct ThreadMessageQueue_t
{
	Uint32				uiThreadID;
	ThreadMessage_t*	pQueueSlots;
	int					nSize;
	int					nItems;
	SDL_mutex*			pMutex;
};

struct Handle_t
{
	SDL_Thread*				pThread;
	ThreadFunc				tf;
	void*					pData;
	ThreadMessageQueue_t*	pMsgQueue;
};

ThreadMessageQueue_t*	CreateThreadMessageQueue(int nDesiredSize)
{
	ThreadMessageQueue_t* pMsgQueue = (ThreadMessageQueue_t*)malloc(sizeof(ThreadMessageQueue_t));
	if (pMsgQueue == NULL)
	{
		TRACE("CreateThreadMessageQueue() - unable to allocate bytes for message queue.\n");
		return NULL;
	}
	
	pMsgQueue->pQueueSlots = (ThreadMessage_t*)malloc(sizeof(ThreadMessage_t)*nDesiredSize);
	if (pMsgQueue->pQueueSlots == NULL)
	{
		TRACE("CreateThreadMessageQueue() - unable to allocate bytes for message queue.\n");
		free(pMsgQueue);
		return NULL;
	}
	
	pMsgQueue->pMutex = SDL_CreateMutex();
	if (pMsgQueue->pMutex == NULL)
	{
		TRACE("CreateThreadMessageQueue() - unable to create Mutex for message queue.\n");
		free(pMsgQueue->pQueueSlots);
		free(pMsgQueue);
		return NULL;
	}
	
	pMsgQueue->uiThreadID = SDL_ThreadID();
	pMsgQueue->nItems = 0;
	pMsgQueue->nSize = nDesiredSize;
	
	return pMsgQueue;
}

int DeleteThreadMessageQueue(ThreadMessageQueue_t* pMessageQueue)
{
	SDL_mutex* pMutex;
	
	if (pMessageQueue != NULL)
	{
		if (pMessageQueue->uiThreadID != SDL_ThreadID())
		{
			TRACE("DeleteThreadMessageQueue() - caller thread ID should be the same as MessageQueue thread ID.\n");
			return -1;
		}
		
		pMutex = pMessageQueue->pMutex;
		
		if (SDL_mutexP(pMutex) == -1)
		{
			TRACE("DeleteThreadMessageQueue() - Couldn't lock mutex\n");
			return -1;
		}
	
		if (pMessageQueue->nItems > 0)
		{
			TRACE("DeleteThreadMessageQueue() - deleting thread message queue with %d items still there.\n", pMessageQueue->nItems);
		}
	
		free(pMessageQueue->pQueueSlots);
		pMessageQueue->pQueueSlots = NULL;
		pMessageQueue->nItems = 0;
		pMessageQueue->nSize = 0;
		free(pMessageQueue);

		SDL_mutexV(pMutex);
		SDL_DestroyMutex(pMutex);
	}
	return 0;
}

int PushThreadMessage(ThreadMessageQueue_t* pMessageQueue, ThreadMessage_t* pMsg)
{
	if (pMessageQueue != NULL)
	{
		if (SDL_mutexP(pMessageQueue->pMutex) == -1)
		{
			TRACE("PushThreadMessage() - Couldn't lock mutex\n");
			return -1;
		}
		
		if (pMessageQueue->nItems >= pMessageQueue->nSize)
		{
			TRACE("PushThreadMessage() - Queue is full.\n");
			SDL_mutexV(pMessageQueue->pMutex);
			return -1;
		}
		
		memcpy(&(pMessageQueue->pQueueSlots)[pMessageQueue->nItems], pMsg, sizeof(ThreadMessage_t));
		pMessageQueue->nItems++;
		
		TRACE("PushThreadMessage() - Added message (code 0x%x) at position %d.\n", pMessageQueue->pQueueSlots[pMessageQueue->nItems-1].iMsgCode, pMessageQueue->nItems-1);
		
		SDL_mutexV(pMessageQueue->pMutex);
	}

	return 0;
}

int PopThreadMessage(ThreadMessageQueue_t* pMessageQueue, ThreadMessage_t* pMsg)
{
	int nPopped = 0;
	
	if (pMessageQueue != NULL)
	{
		if (SDL_mutexP(pMessageQueue->pMutex) == -1)
		{
			TRACE("PopThreadMessage() - Couldn't lock mutex\n");
			return -1;
		}
		
		if (pMessageQueue->nItems > 0)
		{
			memcpy(pMsg, &(pMessageQueue->pQueueSlots)[0], sizeof(ThreadMessage_t));
			pMessageQueue->nItems--;
			if (pMessageQueue->nItems > 0)
			{
				memmove(&(pMessageQueue->pQueueSlots)[0], &(pMessageQueue->pQueueSlots)[1], sizeof(ThreadMessage_t)*pMessageQueue->nItems);
			}
			nPopped = 1;
		}
		SDL_mutexV(pMessageQueue->pMutex);
	}

	return nPopped;
}

int ThreadSurrogateProc(void* pData)
{
	int ret = -1;
	Handle_t* pHandle = (Handle_t*)pData;
	
	pHandle->pMsgQueue = CreateThreadMessageQueue(MESSAGE_QUEUE_SIZE);
	if (pHandle->pMsgQueue != NULL)
	{
		ret = pHandle->tf(pHandle, pHandle->pData);
	}
	DeleteThreadMessageQueue(pHandle->pMsgQueue);
	return ret;
}

Handle_t* CreateThread(ThreadFunc tf, void* pData)
{
	Handle_t* pHandle = (Handle_t*)malloc(sizeof(Handle_t));
	if (pHandle != NULL)
	{
		pHandle->tf = tf;
		pHandle->pData = pData;
		pHandle->pThread = SDL_CreateThread(ThreadSurrogateProc, pHandle);
	}
	return pHandle;
}

void DeleteThread(Handle_t* pHandle)
{
	SDL_KillThread(pHandle->pThread);
	free(pHandle);
}

int PostThreadMessage(Handle_t* pHandle, ThreadMessage_t* pMsg)
{
	return PushThreadMessage(pHandle->pMsgQueue, pMsg);
}

int GetMessage(Handle_t* pHandle, ThreadMessage_t* pMsg)
{
	return PopThreadMessage(pHandle->pMsgQueue, pMsg);
}


